Rhapsody Developer Release Copyright 1998 by Apple Computer, Inc. All Rights Reserved.
Contents:
This document started as a paper to let everyone know about the upcoming scripting support. However, there is little concept documentation on two other features that have recently been added to the AppKit and these new features, along with the upcoming scripting support all have a great deal in common conceptually. So this document has been expanded into a discussion of all three of these new features: Scripting, the Document architecture, and Undo support.
This document does not go into great detail about the specifics of the new classes or how to use them. Instead it concentrates on the recommended structure of an application and how that structure supports all these new features. This is especially true when it comes to the Scripting discussions, since the actual classes and APIs are not yet final and this functionality will not yet be available in DR2.
In this document, the first mention of one of the new classes in the Yellow frameworks is in bold type. All these classes should be documented and it is recommended that you take a look at the reference documentation as well as this paper. Classes which are not yet available but will be added to support scripting are in italic type the first time they are mentioned. These classes are not yet in the frameworks and have no reference documentation yet. This paper is the only place to find information on these classes at the moment.
This document uses Objective-C syntax when describing specific API. All the API discussed is available in Java (or will be available in the case of scripting). Any special issues for Java are discussed where appropriate, and if Java isn't mentioned specifically it is because there is nothing special to say about it.
The first major section of this document provides a brief description of the Model-View-Controller pattern as it is used in this paper. Familiarity with this pattern will be assumed in the remaining sections that talk about the specific new features.
The Model-View-Controller (MVC) pattern is quite old. It has been around at least since the early days of Smalltalk. It is a high-level pattern in that it concerns itself with the global architecture of a program and tries to provide a classification of the different kinds of objects that make up an application.
According to the pattern there are three types of objects: model objects, view objects, and controller objects. The pattern defines the roles that these types of objects play in the application, and, as a developer, you design your classes to fall into these three groups.
This document does not fully describe MVC design pattern in any formal way, since that's not really the purpose here, but it does discuss the three categories a bit in order to give some background for the rest of this paper which describes some of the new features in the AppKit.
Model objects are the data-bearing objects of your application. A well-designed MVC application will have all its important data encapsulated in model objects. Especially, any data that is part of the persistent state of the application (whether that persistent state is stored in files or databases or punch cards) should reside in the model objects once it is loaded into the application.
Ideally, a model object has no connection to the user interface used to present and edit it. This is not always true, and there is some room for flexibility here, but in general a model object should not be concerned with interface and presentation issues.
For example, if you have a model object that represents a person (say you are writing an address book), you might want to store a birth date. That's a good thing to store in your Person model object. However, storing a date format string, or other information on how that date is to be presented, is probably better off somewhere else.
One example where a bit of an exception is reasonable is a Drawing application that has model objects that represent the graphics. It makes sense for the graphic objects to know how to draw themselves since the main reason for their existence is to define a visual thing. But even in this case, the graphic objects should not rely on living in a particular view or any view at all, and they should not be in charge of knowing when to draw themselves. They should be asked to draw themselves by the view object that wants to present them.
A view object knows how to display and possibly edit data from the application's model. Ideally, the view should not store the data it is displaying (semantically speaking, of course. It is obviously valid for the view to do caching of data or other such tricks for performance reasons). A view object will often be in charge of displaying just one part of a model object, or a whole model object, or even many different model objects. Views come in many different varieties.
The AppKit is mostly a view framework although recently some of the newer classes are beginning to get into the controller layer and there are also a few model objects in there as well. The Foundation has mostly model objects along with some controller type stuff.
A view should be in charge of ensuring it is displaying the model correctly. This means that it usually needs to hear about changes to the model. Your model objects should not be tied directly to the views, so they will need a generic way of indicating that they have changed. They can either post NSNotifications when they are altered or define another general way that they will pass change notifications up to the views, usually through the controller layer.
A controller object basically acts as the intermediary between the application's view and its model. Typically controller objects have application specific logic in them. Controllers are often in charge of making sure the views have access to the model objects they need to display and often act as the conduit through which views hear about changes to the model.
Controllers are where most of the application specific logic should go. Ideally, by segregating the application specific stuff into the controllers, you make the model and view objects more general and reusable. Controllers are often the least reusable part of an application design, but that's OK. You can't reuse everything and if you keep the non-reusable stuff in the controller layer, you have a much better chance of being able to reuse the other objects.
Often, there is a lot of code that goes into the controller layer. In some cases it is useful to subdivide this layer further into "model-controllers" and "view-controllers". (This split, oddly enough, fits well with the document architecture discussion below, but it's probably just a coincidence.)
A model-controller is a controller that concerns itself mostly with the model layer. It "owns" the model and most of its responsibilities revolve around managing the model and communicating with the view-controller(s).
A view-controller is a controller that concerns itself mostly with the view layer. It "owns" the interface (the views) and most of its responsibilities revolve around managing the interface and communicating with the model-controller.
There will be a little more detail on this distinction in the section on the Document architecture.
Apple is currently in the process of adding lots of new features to the Yellow frameworks. Some of these features are relatively high-level compared to the kind of things that the AppKit has traditionally provided. Because the AppKit and Foundation are starting to offer help to developers in these higher level areas, it is necessary to start getting involved in how the higher levels of an application are designed.
While developers have always been encouraged to use the MVC pattern, with the advent of new features such as the document architecture, undo support, and scriptability, it is more important than ever for application designers to take the MVC pattern to heart. All of these new high-level features will work best if your application design follows the MVC pattern. It should be almost effortless to use these new features if your application has a good MVC separation, but it will take more effort to use the new features if you don't have a good separation.
The rest of this document discusses three big new features. Two are here now (documents and undo); one is coming later (scriptability). All of them are designed to work best and most effortlessly in an MVC setting. The upcoming Scripting support is described in the next section. Then the Document and Undo support are presented.
Scripting support is not yet ready for customers and developers, but a great deal of the design is in place and it is definitely possible for you, as a developer, to start planning ahead for when you will be able to make your application scriptable. The good news is that if you design your application to work well with the new document architecture and other new features of the Yellow frameworks such as Undo, you will be in great shape to take advantage of scriptability.
For the most part this section does not get into specific API since it hasn't yet been finalized. Any specific API that is mentioned is subject to change.\
AppleScript has always aimed at scripting the model layer of an application. This is a good thing. Much of the time, the most efficient way for a script to do something is not the same as the best way for a user to do the same thing. If scripting were concerned with providing access to the UI of an application it would just be glorified journaling.
Of course, there are times when you do want to affect certain aspects of the UI while scripting, usually in scripts that are more like macros.
Some scripts are like batch processing. They go in and do their thing and do not need or want the user's involvement. A scripting system that extracts data from a database, processes it through other applications and then sends it all to a page layout program to automatically generate the classified ads page for a newspaper is an example of such batch processing. The whole idea in these cases is to not involve the user. It is clear that in these cases, you want to go directly to the application's model and just get the work done.
Some scripts are like macros. They do a very specific manipulation of an application, usually a relatively small and self-contained one, and their purpose is to automate a small repetitive task for the user. For instance, a script that gets the selected graphic in a page layout program, adds a caption beneath it and sets up blue-line guides along the outer edges of the resulting group to aid with alignment of other items would be a script of this type. The key attribute of these scripts is that the user does a little preparation (like selecting the graphic), invokes the script, then continues on when it is done. It is for this type of script that your application may wish to also expose some of its UI concepts to scripters. Things such as windows, selections, etc should be made scriptable only in order to enable this type of scripting. And exposing these UI concepts should be an addition to the full support for directly scripting the model of your application.
The scriptability support in the Yellow frameworks will be geared towards making it easy for an application to expose its model objects to scripters. It will be easiest if the actual model objects that your application uses are the objects it wants to support for scripting. When you are designing your model layer, you should keep this in mind. Remember that scripters don't generally consider themselves programmers (although if you probe a decent scripter you will usually find they have many of the same skills as a programmer). If you keep in mind that you will be wanting to expose a subset of the model classes of your application to scripters, you will, in the end, be much better prepared to add scriptability.
Another thing to keep in mind is that many simple apps tend to keep state in their UI. For instance, a Preferences panel controller might be implemented so that the only storage for the setting of a boolean preference is in the state of the checkbox in the preferences panel (retrieved and set with the -state/-setState: methods). First, this is generally not a good strategy for data that is part of a document's model because it is antithetical to the MVC pattern. For certain auxiliary panels, though, sometimes this is simpler, and so sometimes it is done. If this state needs to be able to be accessed and modified from a script, it should probably be separated from the UI and stored in your controller if it doesn't belong in your model. In many cases this separation is necessary or desirable even without considering scripting. For example, in the case of a preferences panel controller, if the current preference settings are only stored in the actual controls of the panel, the preferences controller cannot answer any questions about the current settings without loading the panel. If other parts of the application need to be able to query the preferences even if the user has not brought up the preferences panel (a likely situation), then it would be much better if the preferences controller itself stored the settings. This would allow it to avoid having to load a nib file (a somewhat expensive activity) until it is actually needed.
The same argument holds for primitive behaviors as well. For instance, if you have a find panel, instead of implementing the logic to actually perform the find in the action of the "Find Next" button, you should probably define some API on your document class or on your model objects that is capable of performing the find. The "Find Next" button's action could then call this API. The advantage is that when you want to allow finds to be performed by scripts, you can let the script go through the document or model API instead of having to hack up scripted access to the find panel itself, which is less desirable.
Those that have used the Enterprise Objects Framework are familiar with Key-Value Coding (KVC). Scripting will heavily rely on Key-Value Coding to provide automatic scripting support where possible.
For those that have never used Key-Value Coding, it is a very simple concept. Each model object defines a set of "keys" that it supports. A key represents a specific piece of data that the model object has. Some examples of keys that will probably be familiar to those who have used AppleScript are "words", "font", "documents", "color", and so on. The Key-Value Coding API provides a generic and automatic way to query an object for the values of its keys and to set new values for its keys. The primitive methods for KVC are -valueForKey: and -takeValue:forKey:. NSObject has generic implementations of these methods that will look for and use either standard accessor method pairs such as -color and -setColor: for the key named "color" or will even go directly to the instance variable "color" in your class if it does not implement the accessors. KVC defines many other extended methods that are implemented in terms of the two primitives as well, but these don't need to be discussed here since they have little bearing on how you will implement scripting support.
You should define the set of keys for your model objects and implement the accessor methods. As of now, there is no need for your keys to be formally declared anywhere, but as you design your objects, think about what their keys should be and write it down somewhere as part of your design. If you do this, then a great deal of scripting support will come for free. More discussion about how you will make these keys known to the scripting frameworks will follow below.
Keys are subdivided into three categories which have their roots in relational databases (relational database access is the main purpose of the Enterprise Objects Framework.) While the history of this subdivision goes back to relational databases, the subdivision makes sense in many situations, including scripting. Keys are either Attribute keys, To-One Relationship keys, or To-Many Relationship keys. In AppleScript parlance, these key types pretty much map to Properties and Elements. Think of AppleScript Elements as Relationship keys (where no distinction is made between to-one and to-many relationships) and think of AppleScript Properties as Attribute keys.
So, why is Key-Value Coding so important for scripting? AppleScript defines "object hierarchies" which define the structure of the model objects in an application. For instance, a Drawing application has documents and those documents have graphic objects. The graphic objects in turn have a fill color and line thickness. Most AppleScript commands reference a set of objects within your application by drilling down this object hierarchy. For instance some graphics might be identified by saying "graphics 5 thru 7 of the document 'MyDocument' of application 'MyDraw'". There has to be some way of finding these graphics so they can be acted upon. With KVC this can be pretty much entirely automatic. An application has the key "documents" which is a to-many relationship (because the application can open multiple documents). Each document has a "name" key which identifies the file it represents. To find the document named "MyDocument" the framework can ask for all the documents of the application and check each one's name until it finds the one named "MyDocument". Because KVC defines a uniform way of asking for the value of a key (-valueForKey:) all this work can be done totally automatically with no work by the developer. Similarly, once the document is found, the "graphics" key is obtained and elements 5 thru 7 are found.
Those familiar with AppleScript will recognize that the above discussion is talking about stuff that would normally be accomplished by the Object Support Library. Yellow AppleScript support will have its own Object Support Library replacement which knows how to use KVC to evaluate object references. Instead of specifically invoking the library and passing in all sorts of evaluation handlers, the KVC mechanism will be used and the developer's part in having to handle these evaluations will be minimized.
Of course, there will be ways to get more directly involved in the evaluation if you need to do so for performance reasons or if your scripting model does not match your internal model closely enough for the automatic support to work.
The usefulness of Key-Value Coding does not stop with object reference evaluation. Most of the core commands defined by AppleScript will have default implementations based on KVC. For instance, the Get Data and Set Data commands will require no extra code for your objects to support if they define their keys properly and implement the standard accessors. The same goes for Move, Clone, Delete, Create, Count, and Exists. Script commands which can be generically implemented with KVC will be and most model objects will not have to worry about them at all.
Of course, if your model object needs to be able to handle some commands specifically, even if they do have default implementations, they can arrange to do so.
Object references were mentioned above in the discussion of Key-Value Coding. Object references in AppleScript are expressions such as "words whose color is red of the fourth paragraph of the front document of application 'TextEdit'". Object references in Yellow applications will be represented by the NSObjectReference class. There are concrete subclasses of this abstract class to represent the different reference forms supported by AppleScript such as index references ("word 5"), test references ("whose" clauses), and others.
A script command is an atomic action in AppleScript. Script commands are what an application receives when it is being scripted. It may receive many of them consecutively, but each one is separate, distinct and complete. Script commands are represented by NSScriptCommand objects in the Yellow frameworks. Sometimes there are subclasses of NSScriptCommand (if the command has a default implementation based on KVC, it will have a specific subclass that implements that default behavior.) But it is not required that NSScriptCommand be subclassed.
An NSScriptCommand has an object reference that identifies the receiver(s) of the command and it has whatever arguments are supported by the command. Command arguments can be optional or required. Command arguments can be actual values or object references that identify where to find the actual values within the application's object hierarchy.
A scriptable object declares what commands it supports. For commands that have a default implementation, scriptable objects can choose to use it, or they can choose to implement support for the command themselves. For commands without default implementations, scriptable objects must do their own implementation if they wish to support the command.
It may seem somewhat odd that script commands are separate from the classes that support them, but this is in the nature of AppleScript. AppleScript wants to have a small set of commands that act on a wide set of objects and therefore it defines the commands separately. This, at least at first, seems at odds with object oriented design where you want the behavior and the data rolled up into an a single entity, an object. However, there are some advantages to having them separate in this case even though the issue is to some degree forced on the frameworks by the nature of AppleScript. Having them separate gives the Yellow frameworks the ability to support default implementations for commands that are generic enough to be implementable through KVC.
In AppleScript, chunks of scriptability API are grouped together into suites. Suites consist of a set of class definitions and a set of command definitions. In traditional MacOS, the suites an application supports are defined in the 'aete' resource of the application. In the Yellow frameworks, suites are defined in a property list. At first, there are likely to be no special tools for creating this property list. Instead you will have to create the files by hand. Each script suite gets its own property list and any bundle can declare script suites. The set of suites an application supports is made up of the union of all the suites defined by the application itself, the frameworks it links against, and the bundles it loads dynamically. Some suites will be declared by the standard Yellow frameworks and will automatically be supported by any scriptable application. The Core suite and the Text suite are examples of automatically supported suites. If you expose access to an NSTextStorage through your object hierarchy, that NSTextStorage will be fully scriptable through the standard Text suite automatically. If your application uses the new document architecture discussed below, it will automatically support all the Core suite commands that can be applied to documents.
The property list that describes a suite contains all the information about the classes and commands in that suite that are needed by the scripting frameworks. For classes, this includes all the supported keys (attribute and relationship keys) for the class and their types. It also includes all the commands that the class supports (both from the class' own suite and others). For commands this includes the number and types of the arguments as well as whether or not they are required and the return data type.
Another sort of information in the suite definition is the information needed to map the classes and commands to the appropriate four-letter codes used to structure the data in an AppleEvent which represents a script command. The scripting support will use AppleEvents as its transport in order to provide interscriptability between the Blue and Yellow environments. Yellow application developers should not have to ever deal with an AppleEvent directly to support scripting, but they will have to provide the information necessary to map classes, commands, keys, etc... to the codes used in AppleEvents.
Finally, in addition to the suite definition, there will be terminology information that identifies the actual scripting language vocabulary to be used for the various classes and commands.
The Yellow frameworks themselves will define several of the standard suites including at least the Core and Text suites. In addition Yellow framework classes will implement scriptability for these standard suites so that, for instance, the NSTextStorage object will be completely scriptable using the Text suite and the NSDocumentController and NSDocument objects will support the Core scripting commands that make sense for documents.
Any application can define its own suites. In these suites they can define new script object classes and/or new script commands.
Recordability is still under investigation, but is likely to take a shape similar to the way undo works (see the Undo discussion below). The current proposal is that approximately in the same places that you will register an action name with the undo manager for a user initiated action, you will also construct a script command which represents the action for recording. (See the section below on Undo support for a discussion of where you should assign action names for undo.)
This area is still under investigation, but eventually there should be a way of executing script commands or even whole scripts directly from your application that script your application, other applications or a combination of the two.
The new document architecture in the AppKit is made up of three classes: NSDocument, NSWindowController, and NSDocumentController. NSDocument is the main class. It represents a single document in your application. Developers must subclass NSDocument to give it knowledge of the application's model layer and to implement persistence (loading and saving). NSWindowControllers own and control the UI. An NSDocument has one or more NSWindowControllers. Developers often subclass NSWindowController to add specific knowledge of the UI that the controller is meant to manage. NSDocumentController is a singleton class. Each document based application has a single instance of NSDocumentController which keeps track of and manages all the open documents. Developers typically don't need to subclass NSDocumentController.
NSDocument is a "model-controller" class. Its main job is to own and manage the model objects that make up a document and to provide a way of saving those objects to a file and reloading them later. An NSDocument should ideally own the document's model objects. Any and all objects that are part of the persistent state of a document should be considered part of that document's model. Sometimes the NSDocument itself has some data which would be considered part of the model. For example, in a Draw application, the NSDocument subclass might have an array of Graphic objects which make up the model of the document. In addition to the actual Graphic objects, the Document contains some data which should technically be considered part of the model since the order of the graphics within the document's array matters in determining the front-to-back ordering of the Graphics.
An NSDocument should not contain or require the presence of any objects that are specific to the UI. While a document can have and manage NSWindowController objects which present the document visually and allow the user to edit it, it should not depend on these objects being there. There are cases where it might be desirable to have a document open in your application without having it visually displayed. For instance, a script might have opened a document to do some processing on it. If the script does not need the user to become involved in the processing, the script might want the document to be opened, manipulated, saved, and closed again, without it ever appearing on screen.
NSWindowController is a "view-controller" class. Its main job is to own and manage the view objects that are used to display and edit a document. A document that is visible to the user will have one or more NSWindowControllers which own and manage the visual presentation. While you can use an NSWindowController directly, a lot of the time you will subclass it to add specific knowledge of the interface it controls. An NSWindowController usually gets its interface from a nib file. Subclasses often add outlets and actions for the controls and views within the nib file and the NSWindowController usually acts as the "File's Owner" for the nib. In very simple cases where there is only one window for a document, you may want your NSDocument class to have outlets and actions for the nib. In this case, the NSDocument subclass will act as the "File's Owner" for the nib, but it will still create an NSWindowController to own and manage the objects that are loaded from the nib.
NSDocumentController manages documents. It keeps track of all open documents, it knows how to create new documents and how to open existing documents. It knows how to find open documents given either a window that is part of the document or the path of the file a document was loaded from. For the most part developers won't have to worry about what it does. NSDocumentController knows how to read and use the meta-data that a document based application provides about the types of documents it can open. NSDocumentController can provide information based on that meta-data such as lists of file types supported by an application and which NSDocument subclasses are used for them.
All document based applications declare information about the document types they support in the Info.plist of the application. Currently, there are no development tools that directly support the creation of this meta-data, so you must create it by hand. See the NSDocumentController class spec sheet for details on the Info.plist keys required by the document architecture and how to include this meta-data in your application project.
Briefly, though, the meta-data declares the "types" supported by an application. There is a list of abstract types (which are usually the same as the pasteboard type that represents such data.) For each abstract type, specific information is listed such as what file extensions are used to identify files of that type, what MacOS four-letter type code is used for files of that type, what icon is used by the Workspace to display files of that type, and what subclass of NSDocument is used by your application to deal with files of that type.
NSDocumentController loads all this meta-data type information and uses it. When NSDocumentController runs an open panel it obtains the list of all file extensions that the types your application can read use and passes that list to the open panel so that it can show the openable files. When the user actually chooses a file to open, the NSDocumentController uses the meta-data to determine what subclass of NSDocument to create to load that file.
There are three main ways that you can use the document architecture. They can be ordered from the simplest to the most complex.
The simplest way to use the architecture is good for documents that have only one window and are relatively simple so that there isn't much benefit in splitting the controller layer into a model-controller and a view-controller. In this case there is a subclass of NSDocument only. The NSDocument subclass will provide storage for the model and the ability to load and save as usual. It will also have any outlets and actions required for the UI. It overrides -windowNibName to return the nib file name used for documents of this type. NSDocument will automatically create an NSWindowController to manage that nib file, but the NSDocument itself will serve as the nib's "File's Owner".
If your document only has one window, but it is complex enough that you'd like to split up some of the logic in the control layer, you will want to subclass NSWindowController as well as NSDocument. In this case, any outlets and actions and other behavior that is specific to the UI management will go into the NSWindowController subclass. Your NSDocument subclass will override -makeWindowControllers instead of -windowNibName. -makeWindowControllers should create an instance of your NSWindowController subclass and call [self addWindowController:myWindowController]. In this case, the NSWindowController will be the "File's Owner" for the nib file. Doing this allows for better separation between the UI-related logic and the model-related logic. This approach is recommended for all but the most simple cases.
Finally, if your document requires multiple windows (or allows multiple windows) you will need to subclass NSWindowController as well as NSDocument. In your NSDocument subclass you will override -makeWindowControllers just as for the second usage pattern above, but in this case you may be creating more than one instance of NSWindowController, possibly of different subclasses. Some applications need several different windows to represent one document. In this case you will probably have several different subclasses of NSWindowController and you will create one of each in -makeWindowControllers. Some applications will only need one window for a document, but want to allow the user to create several copies of the window for a single document (sometimes this is called a multiple view document) so that the user can have each window scrolled to a different position, or displayed in different ways. In this case, your -makeWindowControllers may only create one NSWindowController, but there will be a menu command or something that allows the user to create others.
Scripting support will be the most automatic for applications based on the new document architecture for several reasons. First, NSDocument and the other classes of the document architecture will directly implement the standard Document scripting class and will automatically support many of the scripting commands that apply to documents. Second, because the document architecture is built to work with application designs that use MVC separation, and because scripting support depends on many of the same design points, applications that use the document architecture will already be in better shape to support scripting than other applications which are not designed that way. Finally, the document plays an important role in the scripting API of most applications, and NSDocument knows how to fill that role and provides a good starting off point for allowing scripted access to the model aspects of your application.
It will be possible to make an application which is not based on the document architecture scriptable, but it will not be as easy, if only because you will have to duplicate the work you would get for free if the application used the document architecture.
Another new feature in the Yellow frameworks is support for implementing Undo. NSUndoManager is a new class which is basically the EOUndoManager from the Enterprise Objects Framework with some additions and slight changes. NSUndoManager is responsible for keeping track of the actions necessary to undo changes that are made to a document.
The basic premise of the undo architecture is that when you are about to do something you first tell the NSUndoManager how to undo it. The main API is invocation based, so if you have a -setColor: method, it can call [[undoManager prepareInvocationWithTarget:self] setColor:oldColor] before it actually sets the new color. This call will cause an NSInvocation to be created and if the user chooses Undo that invocation (of the method setColor: with the parameter being the old color) will be invoked.
Because there may be many discrete changes involved in a user-level action, all the undo registrations that happen during a single pass of the event loop are usually grouped together and are undone all at once. There is API on NSUndoManager that allows you to control the grouping behavior further if you need to.
If you use the document architecture, some aspects of undo handling happen automatically. By default, each NSDocument has an NSUndoManager. If you won't be supporting Undo, you can use -setHasUndoManager: on NSDocument to keep it from being created. You can use -setUndoManager: if you need to use a subclass, or if you otherwise need to change the undo manager to be used by the document.
When an NSDocument has an NSUndoManager it automatically keeps the document's edited state up to date by watching notifications from the undo manager which tell it when changes are done, undone, or redone. In this case, it should never be necessary to directly call the -updateChangeCount: API in NSDocument, since it is all taken care of automatically.
The important thing to remember about supporting undo in a document based application is that all changes that affect the persistent state of the document need to be undo-able. With a multi-level undo architecture, this is very important. If it is possible to make some changes to the document which cannot be undone, then the chain of edits that the NSUndoManager keeps for the document can become inconsistent with the document state. For example, imagine that you have a draw program which is able to undo a resize, but not a delete. If the user selects a graphic and resizes it, the NSUndoManager gets an invocation that can undo that resize operation. Now the user deletes that graphic (which is not recorded for undo). If they now try to undo, at the very least, nothing would happen since the graphic that was resized is no longer there and undoing the resize can't have any visual effect. At worst, the application might crash trying to message a freed object.
So, when implementing undo, remember that everything that causes a change to the document should be undo-able.
The most important code you write to support undo should be in your model layer. Each model object in your app should be able to register undo invocations for all of its primitive methods which change the object.
It is often useful to structure your model object API to consist of primitive methods and extended methods. Examples of this sort of separation can be found all over the Foundation framework (NSString, NSArray, NSDictionary, etc...) If you have such a separation in your model objects, remember that only the primitives should register for undo since, by definition, the extended methods will be implemented in terms of the primitives.
In some cases, it may be necessary to temporarily suspend undo registration for certain actions. For example, a Drawing application lets the user resize a graphic by grabbing a resize knob and dragging it. During this dragging, hundreds or thousands of changes may be made to the bounds rectangle of the selected graphic. Changing the bounds of a graphic is a primitive operation and would normally result in an undo registration. While the user is actively resizing, though, it would be better if those thousands of undo registrations did not happen. In these cases, your model object might provide API to temporarily suspend and resume some or all of its undo registration. It is up to the application developer to decide how to handle this. Certainly, it would work if those thousands of undo registrations did happen, but it would be a tremendous waste of memory to have to remember all those intermediate rectangles when you will never have to restore one of those intermediate states.
While the most important part of your undo support should be in the model, there are two situations where you will need some undo-related code in either your controller or view objects.
The first case is when you want the Undo and Redo menu items to have more specific titles. You can use NSUndoManager's setActionName: to give a name to the current undo group. The last name set wins if more than one is set. These names should reflect the semantic user action, not the primitive operation(s) that the action results in. Therefore, it is in your user action handlers that you should set action names.
If you do not name an undo group, it is no big deal. The menu items will just say "Undo" and "Redo" without being specific about what is to be undone or redone. But when you do register a name it can help the user to know what will be undone or redone. It isn't too hard to sprinkle a few calls to setActionName: in your view or controller action messages, so it is recommended that you try to give meaningful action names.
The second case where you will have some undo code in the controller or view layers is when there are some things that change, which do not affect the actual state of the document, but that still need to be able to be undone. Undoing selection changes is often this way. For example, a Drawing application might not consider the selection to be a part of the document. In fact, if the document can have multiple views open on it, you might be able to have different selections in each one. However, you might want changes in the selection to be able to be undone for the user's convenience and for visual continuity when the user is actually undoing things. In this case, the view which displays the graphics might keep track of the selection. It should register undo invocations as the selection changes.
One important thing to remember about the case where some controller or view layer things must be undo-able is that it is often possible for controller and view objects to come and go during the lifetime of a document object. Now, your model objects typically live for the lifetime of the document and the document also owns the undo manager, so you don't generally have to worry about what happens when the model goes away, but you may have to worry about what happens when the controller and/or view goes away. If your controller or view object registers any undo invocations, you should make sure that they are cleared from the undo manager when the controller or view is deallocated. You can use the NSUndoManager API -removeAllActionsWithTarget:. Once a particular view on your document is closed, it is no longer interesting to keep undo information about view-specific things such as selection changes for that view.
In most cases, it is desirable to have scripted changes be able to be undone as well as user interface changes. This is just one more reason that your primary undo support should be in your model objects. Since scripting is usually directed at the model, if your undo support is in your model primitives, then scripted changes can be undone. Being able to undo scripted changes is actually most important with macro-like scripts where the script is being used to automate relatively small tasks that are interspersed with direct user manipulation. Especially in these cases, you want the scripted changes recorded along with the direct user changes for the same reason that it is important to have all changes to a document recorded. If this is not the case, it is possible for the document to get out of synch with the undo stack.
Currently, the full invocation based API on NSUndoManager is not available through Java. There will eventually be a way of getting the same sort of functionality available in Objective-C through the -prepareInvocationWithTarget: API, but for now, only the simpler registerUndoWithTarget() API (-registerUndoWithTarget:selector:object: in Objective-C) is available in Java. Without the invocation based API it is not possible to register more complex methods to be called for undo from Java. This short-coming will be addressed in a future release.
The DR2 release adds some significant new features to the Yellow frameworks which should significantly reduce and simplify the amount of code that you need to write for your applications. More new features will be coming later including Scripting support and undoubtedly others.
As new high-level features are added to the frameworks, they will be based on the concepts of the Model-View-Controller pattern. Applications which use this pattern well in their design will have significant advantages as they start to make use of these features.
For application developers starting new projects there should be no question that you should attempt to structure your application using the MVC pattern. In the long run it will make your app simpler and make adopting new framework features much easier and in may cases automatic.